package edu.utexas.esel.jgraphpad.archpadplugin;

import java.util.HashMap;
import java.util.Map;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultPort;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;

import com.jgraph.editor.JGraphEditorDiagram;
import com.jgraph.editor.JGraphEditorFile;
import com.jgraph.editor.factory.JGraphEditorDiagramPane;
import com.jgraph.pad.action.JGraphpadFileAction;
import com.jgraph.pad.util.JGraphpadFocusManager;

import edu.utexas.esel.jgraphpad.archpadplugin.graph.ElementGraphCell;
import edu.utexas.esel.jgraphpad.archpadplugin.model.ArchPadDiagram;
import edu.utexas.esel.jgraphpad.archpadplugin.model.Element;
import edu.utexas.esel.jgraphpad.archpadplugin.model.ElementTreeNode;
import edu.utexas.esel.jgraphpad.archpadplugin.model.Element.ElementDeclaration;
import edu.utexas.esel.jgraphpad.archpadplugin.persistence.ArchPadXMLPersistenceManager;

/**
 * This class contains utility functions exposed as static methods.
 *
 * @author svetoslavganov@mail.utexas.edu
 *
 */
public class Utils {

	private Utils() {
		/* do nothing */
	}

	/**
	 * A helper method that crops string to the specified length,
	 * adding 3 dots if there were more characters.
	 *
	 * @return The chopped string.
	 */
	public static String chopString(String string, int max) {
		if (string != null) {
			if (string.length() > max)
				string = string.substring(0, max) + "...";
		}
		return string;
	}

	/**
	 * Returns if the current document is an ArchPad model.
	 *
	 * @return True if the file is an ArchPad document, false otherwise.
	 */
	public static boolean isCurrentArchPadDocument() {
		JGraphEditorFile file =
			JGraphpadFileAction.getPermanentFocusOwnerFile();
		if (file != null) {
			String fileName = file.getFilename();
			return fileName.endsWith(
				ArchPadXMLPersistenceManager.TYPE_ARCHPAD_XML) 
				|| fileName.endsWith(
				ArchPadXMLPersistenceManager.TYPE_ARCHPAD_XML_COMPRESSED);
		}
		return false;	
	}

	/**
	 * Returns if the current diagram is an ArchPad diagram.
	 *
	 * @return True if ArchPad diagram, false otherwise.
	 */
	public static boolean isCurrentArchPadDiagram() {
		return (getPermanentFocusOwnerArchPadDiagram() != null);
	}

	/**
	 * Convenience method for getting the {@link Element} set as the
	 * user object in a {@link DefaultGraphCell}.
	 *
	 * @param cell The cell.
	 * @return The {@link Element} set as the user object if the
	 *         <code>cell</code> is a {@link ElementGraphCell}, null
	 *         otherwise.
	 */
	public static Element getCellElement(Object cell) {
		if (cell instanceof ElementGraphCell) {
		    ElementGraphCell gCell = (ElementGraphCell) cell;
		    return (Element) gCell.getUserObject();
		}
		return null;
	}

	/**
	 * Convenience method for getting the root {@link ElementTreeNode} of an
	 * {@link Element} set as the user object in a {@link DefaultGraphCell}.
	 *
	 * @param cell The cell.
	 * @return The {@link Element} set as the user object if the
	 *         <code>cell</code> is a {@link DefaultGraphCell}, null
	 *         otherwise.
	 */
	public static ElementTreeNode getCellElementRoot(Object cell) {
		Element element = getCellElement(cell);
		if (element != null) {
		    return (ElementTreeNode) element.getRoot();
		}
		return null;
	}

	/**
	 * Sets the value of the attributes under <code>keys</code> to
	 * <code>values</code> for each of the <code>cells</code>.
	 * For each cell[i], for the key[i] the corresponding
	 * value[i] is assigned.If value[i] is null then this will add a
	 * {@link GraphConstants#REMOVEATTRIBUTES} entry to the nested
	 * map and remove the corresponding key.
	 * 
	 * Note: Use only one value of null per array of values, as previous
	 *       {@link GraphConstants#REMOVEATTRIBUTES} will be overwritten by
	 *       later ones.
	 * 
	 * @param cache The {@link GraphLayoutCache} to perform the operation in.
	 * @param cells The cells whose attributes to set.
	 * @param keys The names of the attributes to set.
	 * @param values The values of the attributes to set.
	 */
	public static void setAttributes(GraphLayoutCache cache, Object[] cells,
			String[] keys, Object[] values) {
		Map<String, Object> change =
			new HashMap<String, Object>(keys.length);
		for (int i = 0; i < keys.length; i++) {
			if (values[i] != null) {
				change.put(keys[i], values[i]);
			} else {
				GraphConstants.setRemoveAttributes(change,
						new Object[] { keys[i] });
			}
		}
		cache.edit(cells, change);
	}

	/**
	 * Returns the permanent focus owner graph.
	 *
	 * @return Returns the permanent focus owner.
	 */
	public static JGraph getPermanentFocusOwnerGraph() {
		JGraphpadFocusManager manager = JGraphpadFocusManager
		    .getCurrentGraphFocusManager();
        return manager.getFocusedGraph();
	}

	/**
	 * Returns the permanent focus owner {@link GraphLayoutCache}.
	 *
	 * @return Returns the permanent focus owner.
	 */
	public static GraphLayoutCache getPermanentFocusOwnerGraphLayoutCache() {
		return getPermanentFocusOwnerGraph().getGraphLayoutCache();
	}

    /**
     * Returns if an {@link ElementGraphCell} is a component i.e. its type is
     * {@link ElementGraphCell#TYPE_COMPONENT}.
     *
     * @param cell The cell.
     * @return True if the cell is a component, false otherwise.
     */
	public static boolean isComponent(ElementGraphCell cell) {
		String type = cell.getAttribute(
			ElementGraphCell.ATTRIBUTE_ELEMENT_TYPE);
		return (type == ElementGraphCell.TYPE_COMPONENT);		
	}

    /**
     * Returns if an {@link ElementGraphCell} is a connector i.e. its type is
     * {@link ElementGraphCell#TYPE_CONNECTOR}.
     *
     * @param cell The cell.
     * @return True if the cell is a connector, false otherwise.
     */
	public static boolean isConnector(ElementGraphCell cell) {
		String type = cell.getAttribute(
			ElementGraphCell.ATTRIBUTE_ELEMENT_TYPE);
		return (type == ElementGraphCell.TYPE_CONNECTOR);		
	}

    /**
     * Returns if an {@link ElementGraphCell} is a region i.e. its type is
     * {@link ElementGraphCell#TYPE_REGION}.
     *
     * @param cell The cell.
     * @return True if the cell is a region, false otherwise.
     */
	public static boolean isRegion(ElementGraphCell cell) {
		String type = cell.getAttribute(
			ElementGraphCell.ATTRIBUTE_ELEMENT_TYPE);
		return (type == ElementGraphCell.TYPE_REGION);		
	}

	/**
	 * Returns the diagram for the diagram pane that has the focus.
	 *
	 * @return Returns the focused diagram.
	 */
	public static ArchPadDiagram getPermanentFocusOwnerArchPadDiagram() {
		JGraphEditorDiagram diagram = getPermanentFocusOwnerDiagram();
		if (diagram instanceof ArchPadDiagram) {
			return (ArchPadDiagram) diagram;
		}
		return null;
    }

	/**
	 * Returns permanent focus owner diagram.
	 *
	 * @return Returns the focused diagram.
	 */
	public static JGraphEditorDiagram getPermanentFocusOwnerDiagram() {
		JGraphEditorDiagramPane diagramPane =
			getPermanentFocusOwnerDiagramPane();
		if (diagramPane != null) {
			return diagramPane.getDiagram();
		}
		return null;
	}

	/**
	 * Returns the diagram pane that contains the permanent focus owner.
	 */
	public static JGraphEditorDiagramPane getPermanentFocusOwnerDiagramPane() {
		if (JGraphpadFocusManager.KEEP_FOCUSED_GRAPH) {
			return JGraphEditorDiagramPane.getParentDiagramPane(
					getPermanentFocusOwnerGraph());
		}
		return null;
	}
	
	/**
	 * Gets the name of an {@link Element}. The name of an element is stored
	 * as a child of the root. 
	 *
	 * @param cell The {@link ElementGraphCell} containing the element.
	 * @return The name of the element if such or null.
	 */
	public static String getElementName(ElementGraphCell cell) {
		ElementTreeNode root = getCellElementRoot(cell);
		for (int i = 0, count = root.getChildCount(); i < count; i++) {
		    ElementTreeNode child = (ElementTreeNode) root.getChildAt(i);
		    String childName = child.getDeclaration().getName();
		    if (childName.equals(ElementTreeNode.NODE_NAME_NAME)) {
		        Object value = child.getValue();
		    	return value != null ? value.toString() : null;	
		    }
		}
		return null;
	}

	/**
	 * Sets the name of an {@link Element}. The name of an element is stored
	 * as a child of the root. If the node representing the name does not
	 * exist it is created.
	 *
	 * @param cell The {@link ElementGraphCell} containing the element.
	 * @param name The name.
	 */
	public static void setElementName(ElementGraphCell cell, String name) {
		ElementTreeNode root = getCellElementRoot(cell);

		for (int i = 0, count = root.getChildCount(); i < count; i++) {
		    ElementTreeNode child = (ElementTreeNode) root.getChildAt(i);
		    String childName = child.getDeclaration().getName();
		    if (ElementTreeNode.NODE_NAME_NAME.equals(childName)) {
                child.setValue(name);
                return;
		    }
		}

		ElementDeclaration declaration = Element.getTypeDeclaration(
			ElementTreeNode.NODE_NAME_NAME);
		ElementTreeNode child = new ElementTreeNode(declaration);
		child.setValue(name);

		Element model = getCellElement(cell);
		model.insertNodeInto(child, root, 0);
	}

	/**
	 * Sets the attribute of the root {@link ElementTreeNode} of an
	 * {@link Element} that is stored in a {@link ElementGraphCell}.
	 * </p> 
	 * Note: If the value is null the attribute is removed.
	 *
     * @param cell The cell.
	 * @param attribute The attribute to set.
	 * @param value The value to set.
	 */
	public static void setElementRootAttribute(ElementGraphCell cell,
			String attribute, String value) {
		ElementTreeNode root = getCellElementRoot(cell);
		root.setAttribute(attribute, value);
	}
}
